/**
 * MathHelper
 *
 * Released under Apache License, Version 2.0.
 */

package bsp.helper;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

import bsp.helper.objects.Point2D;

public class MathHelper
{
	protected MathHelper()
	{
	}
	
	public static boolean isEqual(float v1, float v2, float tolerance)
	{
		return v1 == v2;
	}
	
	public static boolean isEqualWithinTolerance(float v1, float v2, float tolerance)
	{
		float val = Math.abs(v2 - v1);
		if ( val >= tolerance ) {
			return false;
		}
		
		return true;
	}
	
	public static double convertDegreeToRadians(float degree)
	{
		return (3.141592653589793D * degree) / 180.0D;
	}
	
	public static double convertRadiansToDegree(float radians)
	{
		return (180.0D * radians) / 3.141592653589793D;
	}
	
	public static String getRoundedNumber(String digits, double number)
	{
		return String.format("%." + digits + "g%n", number);
	}
	
	public static String getRoundedNumber(int digits, double number)
	{
		return String.format("%." + digits + "g%n", number);
	}
	
	public static String getRoundedNumber2Digits(double number)
	{
		DecimalFormat df = new DecimalFormat("#.##");
		return df.format(number);
	}
	
	public static double roundTo2Decimals(double num)
	{
		return Double.valueOf(getRoundedNumber2Digits(num));
	}
	
	/**
	 * Makes a three's rule to normalize a value in a 0..1 interval
	 * 
	 * Example
	 *	 System.out.println("0..1, 0.5 -- " + normalize(0, 1, 0.5, 0, 1));
	 *	 System.out.println("-1..1, 0.5 -- " + normalize(0, 1, 0.5, -1, 1));
	 *	 System.out.println("1..3, 0.5 -- " + normalize(0, 1, 0.5, 1, 3));
	 *	 System.out.println("0..1, 1.5 -- " + normalize(1, 2, 1.5, 0, 1));
	 *	 System.out.println("-1..1, 1.5 -- " + normalize(1, 2, 1.5, -1, 1));
	 *	 System.out.println("1..3, 1.5 -- " + normalize(1, 2, 1.5, 1, 3));
	 * 
	 * @param min
	 *			lowest saturation point. Values below this will return 0
	 * @param max
	 *			highest saturation point. Values above this will return 1
	 * @param value
	 * 
	 * @param start beginning of the normalization interval
	 * @param end ending of the normalization interval
	 * 
	 * @return a double value between start and end
	 * @throws Exception
	 * 
	 */
	public double normalize(double min, double max, double value, double start, double end, double errorValue) /*throws Exception*/
	{
		if (max <= min) {
			/*throw new Exception("Min cannot be higher than max. Values entered are not valid.");*/
			return errorValue;
		}
		if (end <= start) {
			/*throw new Exception("End cannot be higher than start. Values entered are not valid.");*/
			return errorValue;
		}
		if (value >= max) {
			return end;
		}
		if (value <= min) {
			return start;
		}

		double i1 = max - min;
		double i2 = end - start;
		double y = (value - min) * i2 / i1;
		
		return y + start;
	}
	
	/*
	 * Mercator conversion taken from http://stackoverflow.com/questions/14329691/covert-latitude-longitude-point-to-a-pixels-x-y-on-mercator-projection
	 */
	// formula for quarter PI
	private final static double QUARTERPI = Math.PI / 4.0;
	
	/**
	 * Convert latitude and longitude value to screen coordinates x and y
	 */
	public ArrayList<Point2D<Integer>> mercatorConversionForList(int imageWidth, int imageHeight, int mapWidth, int mapHeight, int minimumPadding, List<Double> latitudeValues, List<Double> longitudeValues)
	{
		ArrayList<Point2D<Integer>> outList;
		ArrayList<Point2D<Double>> ptList;
		double latitude, longitude;
		Point2D<Double> ptTemp;
		int size;
		
		// min and max coordinates, used in the computation below
		Point2D<Double> minXY = new Point2D<Double>(-1.0, -1.0);
		Point2D<Double> maxXY = new Point2D<Double>(-1.0, -1.0);
		
		ptList = new ArrayList<Point2D<Double>>();
		outList = new ArrayList<Point2D<Integer>>();
		
		size = latitudeValues.size();
		if ( size < longitudeValues.size() ) {
			size = longitudeValues.size();
		}
		
		for ( int i = 0; i < size; i++ ) {
			longitude = longitudeValues.get(i).doubleValue();
			latitude = latitudeValues.get(i).doubleValue();
			
			// convert to radian
			longitude = longitude * Math.PI / 180;
			latitude = latitude * Math.PI / 180;

			ptTemp = new Point2D<Double>(0.0, 0.0);
			ptTemp.x = longitude;
			ptTemp.y = Math.log(Math.tan(QUARTERPI + 0.5 * latitude));
			
			// The reason we need to determine the min X and Y values is because in order to draw the map,
			// we need to offset the position so that there will be no negative X and Y values
			minXY.x = (minXY.x == -1) ? ptTemp.x : Math.min(minXY.x, ptTemp.x);
			minXY.y = (minXY.y == -1) ? ptTemp.y : Math.min(minXY.y, ptTemp.y);
	
			ptList.add(ptTemp);
		}
		
		// re-adjust coordinate to ensure there are no negative values
		size = ptList.size();
		for ( int i = 0; i < size; i++ ) {
			ptTemp = ptList.get(i);
			
			ptTemp.x = ptTemp.x - minXY.x;
			ptTemp.y = ptTemp.y - minXY.y;

			// now, we need to keep track the max X and Y values
			maxXY.x = (maxXY.x == -1) ? ptTemp.x : Math.max(maxXY.x, ptTemp.x);
			maxXY.y = (maxXY.y == -1) ? ptTemp.y : Math.max(maxXY.y, ptTemp.y);
		}
		
		int paddingBothSides = minimumPadding * 2;

		// the actual drawing space for the map on the image
		mapWidth = mapWidth - paddingBothSides;
		mapHeight = mapHeight - paddingBothSides;

		// determine the width and height ratio because we need to magnify the map to fit into the given image dimension
		double mapWidthRatio = mapWidth / maxXY.x;
		double mapHeightRatio = mapHeight / maxXY.y;

		// using different ratios for width and height will cause the map to be stretched. So, we have to determine
		// the global ratio that will perfectly fit into the given image dimension
		double globalRatio = Math.min(mapWidthRatio, mapHeightRatio);

		// now we need to readjust the padding to ensure the map is always drawn on the center of the given image dimension
		double heightPadding = (imageHeight - (globalRatio * maxXY.y)) / 2;
		double widthPadding = (imageWidth - (globalRatio * maxXY.x)) / 2;
		
		size = ptList.size();
		for ( int i = 0; i < size; i++ ) {
			ptTemp = ptList.get(i);
			
			int adjustedX = (int) (widthPadding + (ptTemp.x * globalRatio));
			// need to invert the Y since 0,0 starts at top left
			int adjustedY = (int) (imageHeight - heightPadding - (ptTemp.y * globalRatio));

			outList.add(new Point2D<Integer>(adjustedX, adjustedY));
		}
		
		return outList;
	}
}

/* end */
